import { GraphicsHelper } from "../../core/utils";
import Bullet from "./bullet";
import { EditorAttr } from "./bullet_editor_attribute";
import { PhysicsMath, BulletSide } from "./physics_math";

const { ccclass, property } = cc._decorator;

const SpringK = 0.02;
@ccclass
export default class Rope extends Bullet {
    particles: cc.Vec2[] = [];
    particleSprings: cc.Vec2[] = [];
    particleLockeds: boolean[] = [];
    _length = 200; //长度
    @EditorAttr("绳子长度")
    @property
    get length() {
        return this._length;
    }
    set length(val) {
        this._length = val;
        if (isNaN(this._length)) {
            this._length = 10;
        }
        this.resetParticles()
    }
    _maxParticleCount = 10;
    @EditorAttr("绳子段数")
    @property
    get maxParticleCount() {
        return this._maxParticleCount;
    }
    set maxParticleCount(val) {
        this._maxParticleCount = val;
        if (isNaN(this._maxParticleCount)) {
            this._maxParticleCount = 3;
        }
        if (this._maxParticleCount < 2) {
            this._maxParticleCount = 2;
        }
        this.resetParticles()
    }
    @property
    get particlePerPixel() {
        return this.length / (this.maxParticleCount - 1);
    }

    @EditorAttr("绳子弹力")
    @property
    spring = 100;

    @property(cc.Graphics)
    drawer: cc.Graphics = null;

    onEnterWorld(): void {
        this.resetParticles();
    }
    resetParticles() {
        let last: cc.Vec2 = null;
        if (this.particles.length !== 0) {
            last = this.particles[this.particles.length];
        }
        this.particleSprings = [];
        this.particles = [];
        this.particleLockeds = [];
        let ppc = this.particlePerPixel;

        for (let i = 0; i < (last ? this.maxParticleCount - 1 : this.maxParticleCount); i++) {
            this.addParticle(cc.v2(i * ppc, 0));
        }
        if (last) {
            this.addParticle(last);
        }

        //todo: debug
        if (this.particles.length !== 0) {
            this.particleLockeds[0] = true;
            this.particleLockeds[this.particles.length - 1] = true;
            this.particles[this.particles.length - 1].set(cc.v2(this.length / 3 * 2, 0))
        };
    }
    addParticle(pos: cc.Vec2) {
        this.particles.push(pos);
        this.particleSprings.push(cc.v2());
    }
    protected update(dt: number): void {
        if (this.world) {

            let damping = 1 - (Math.min(100, Math.max(0, this.damping)) / 100);
            let gravity = this.world.gravity.mul(Math.min(100, Math.max(0, this.gravityScale - Math.min(100, Math.max(0, this.float)))) / 100).mul(dt);
            let ind = 0;
            do {
                let cur: cc.Vec2 = this.particles[ind];
                let curIsLock = this.particleLockeds[ind];
                if (!curIsLock) {
                    let curSpr: cc.Vec2 = this.particleSprings[ind];
                    let prev: cc.Vec2 = this.particles[ind - 1];
                    let next: cc.Vec2 = this.particles[ind + 1];
                    if (prev) {
                        this.calcForce(prev, cur, curSpr, dt);
                    }
                    if (next) {
                        this.calcForce(next, cur, curSpr, dt);
                    }
                    curSpr.addSelf(gravity);
                    curSpr.mulSelf(damping);
                    cur.addSelf(curSpr.mul(dt));
                }
                ind++;
            } while (ind < this.particles.length);

            if (!this.isSensor) {
                let boxSize = 10;
                let boxes: cc.Rect[] = [];
                let bullets: Bullet[] = [];
                if (this.world) {
                    this.world.bullets.forEach(bullet => {
                        if (bullet === this) {
                            return;
                        }
                        boxes.push(bullet.physicsRect);
                        bullets.push(bullet);
                    });
                }
                let elastic = this.elastic / 100;
                for (let i = 0; i < this.particles.length; i++) {
                    if (this.particleLockeds[i]) {
                        continue;
                    }
                    if (boxes.length > 0) {
                        let np = this.particles[i];
                        let npSpr = this.particleSprings[i];
                        let wp = this.curPos.add(np);
                        let wrect = cc.rect(wp.x, wp.y, boxSize, boxSize);
                        for (let boxInd = 0; boxInd < boxes.length; boxInd++) {
                            let box = boxes[boxInd];
                            let side = PhysicsMath.CheckCollision(wrect, box);
                            switch (side) {
                                case BulletSide.Top:
                                    np.y = bullets[boxInd].yMin - this.node.y;
                                    if (npSpr.y > 0) {
                                        npSpr.y *= -elastic;
                                    }
                                    break;
                                case BulletSide.Bottom:
                                    np.y = bullets[boxInd].yMax - this.node.y;
                                    if (npSpr.y < 0) {
                                        npSpr.y *= -elastic;
                                    }
                                    break;
                                case BulletSide.Left:
                                    np.x = bullets[boxInd].xMax - this.node.x;
                                    if (npSpr.x < 0) {
                                        npSpr.x *= -elastic;
                                    }
                                    break;
                                case BulletSide.Right:
                                    np.x = bullets[boxInd].xMin - this.node.x;
                                    if (npSpr.x > 0) {
                                        npSpr.x *= -elastic;
                                    }
                                    break;
                            }
                            if (side !== BulletSide.None) {
                                break;
                            }

                        }
                    }
                }
            }
        }
    }
    calcForce(prev: cc.Vec2, cur: cc.Vec2, curSpr: cc.Vec2, dt: number) {
        let sub = prev.sub(cur);
        if (sub.len() > this.particlePerPixel / 2) {
            let delta2 = sub.len() - this.particlePerPixel / 2;
            curSpr.addSelf(sub.normalize().mul(delta2).mul(this.spring / 100));
        }
    }
    protected lateUpdate(dt: number): void {
        this.drawer.clear();
        let nps = [];
        for (let i = 0; i < this.particles.length; i++) {
            let np = this.particles[i];
            this.drawer.circle(np.x, np.y, 10);
            nps.push(np);
        }
        this.drawer.fill();
        this.drawer.stroke();
        let cnps = catmullRomSpline(nps, 0);
        GraphicsHelper.DrawPoly(this.drawer, cnps, false);

        this.drawer.stroke();
    }
};
//获取插值

function cardinalSplineAt(p0, p1, p2, p3, tension, t) {

    var t2 = t * t;

    var t3 = t2 * t;

    /*

     * Formula: s(-ttt + 2tt - t)P1 + s(-ttt + tt)P2 + (2ttt - 3tt + 1)P2 + s(ttt - 2tt + t)P3 + (-2ttt + 3tt)P3 + s(ttt - tt)P4

     */

    var s = (1 - tension) / 2;

    var b1 = s * ((-t3 + (2 * t2)) - t);                      // s(-t3 + 2 t2 - t)P1

    var b2 = s * (-t3 + t2) + (2 * t3 - 3 * t2 + 1);          // s(-t3 + t2)P2 + (2 t3 - 3 t2 + 1)P2

    var b3 = s * (t3 - 2 * t2 + t) + (-2 * t3 + 3 * t2);      // s(t3 - 2 t2 + t)P3 + (-2 t3 + 3 t2)P3

    var b4 = s * (t3 - t2);                                   // s(t3 - t2)P4

    var x = (p0.x * b1 + p1.x * b2 + p2.x * b3 + p3.x * b4);

    var y = (p0.y * b1 + p1.y * b2 + p2.y * b3 + p3.y * b4);

    return cc.v2(x, y);

};

// 根据输入点数组，获取曲线

function catmullRomSpline(points: cc.Vec2[], alpha: number): cc.Vec2[] {

    const result: cc.Vec2[] = [];

    result.push(points[0]);

    let _minSeg = 10

    for (let i = 0; i < points.length; i++) {

        let start = points[i]

        let end = points[i + 1]

        if (!end) continue

        let dis = start.sub(end).mag()

        let count = dis / _minSeg

        let p0 = getControlPointAt(points, i - 1)

        let p1 = getControlPointAt(points, i - 0)

        let p2 = getControlPointAt(points, i + 1)

        let p3 = getControlPointAt(points, i + 2)

        for (let t = 0; t <= 1; t += 1 / count) {

            const x = cardinalSplineAt(

                p0,

                p1,

                p2,

                p3,

                alpha, t);

            result.push(x);

            // console.log(result.length, x)

        }

    }

    // Add the last point

    return result;

}

// 根据index 获取数据点

function getControlPointAt(controlPoints, pos) {

    var p = Math.min(controlPoints.length - 1, Math.max(pos, 0));

    return controlPoints[p];

};